export type EventType = string | symbol;
export type Handler<T = unknown> = (event: T) => void;
export type WildcardHandler<T = Record<string, unknown>> = (type: keyof T, event: T[keyof T]) => void

export type EventHandlerList<T = unknown> = Array<Handler<T>>
export type WildCardEventHandlerList<T = Record<string, unknown>> = Array<WildcardHandler<T>>

export type EventHandlerMap<Events extends Record<EventType, unknown>> = Map<
  keyof Events | '*',
  EventHandlerList<Events[keyof Events]> | WildCardEventHandlerList<Events>
>

export interface Emitter<Events extends Record<EventType, unknown>> {
  all: EventHandlerMap<Events>

  pending: any

  on<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>): void

  on(type: '*', handler: WildcardHandler<Events>): void

  off<Key extends keyof Events>(
    type: Key,
    handler?: Handler<Events[Key]>,
  ): void;

  off(type: '*', handler: WildcardHandler<Events>): void

  emit<Key extends keyof Events>(type: Key, event: Events[Key]): void

  emit<Key extends keyof Events>(type: undefined extends Events[Key] ? Key : never): void
}

export default function mitt<Events extends Record<EventType, unknown>>(all?: EventHandlerMap<Events>): Emitter<Events> {
  type GenericEventHandler = | Handler<Events[keyof Events]> | WildcardHandler<Events>

  all = all || new Map()
  const pending = new Map()
  const map = new Map()
  const out = {
    all,
    pending,
    on<Key extends keyof Events>(type: Key, handler: GenericEventHandler) {
      out.off(type, handler)
      const handlers: Array<GenericEventHandler> | undefined = all!.get(type)
      if (handlers) {
        handlers.push(handler)
      } else {
        all!.set(type, [handler] as EventHandlerList<Events[keyof Events]>)
      }
      const ps = pending.get(type)
      if (ps) {
        // @ts-ignore
        ps.forEach((args: any) => handler(...args))
        pending.delete(type)
      }
    },
    off<Key extends keyof Events>(type: Key, handler?: GenericEventHandler) {
      const handlers: Array<GenericEventHandler> | undefined = all!.get(type)
      if (handlers) {
        if (handler) {
          handlers.splice(handlers.indexOf(handler) >>> 0, 1)
        } else {
          all!.set(type, [])
        }
      }
    },
    emit<Key extends keyof Events>(type: Key, evt?: Events[Key]) {
      let handlers = all!.get(type)
      if (handlers) {
        (handlers as EventHandlerList<Events[keyof Events]>)
          .slice()
          .map((handler) => {
            handler(evt!)
          })
      } else {
        if (!pending.has(type)) {
          pending.set(type, [])
        }
        pending.get(type)!.push([evt])
      }

      handlers = all!.get('*')
      if (handlers) {
        (handlers as WildCardEventHandlerList<Events>)
          .slice()
          .map((handler) => {
            handler(type, evt!)
          })
      }
    },
  }
  return out
}
